package teleware.soe.core.util;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 扫描工具类
 *
 * @author :hongsw
 * @date :2020-12-14 11:57
 */
public class ScanUtils {
    /**
     * 类加载器
     */
    private static final ClassLoader CL = Thread.currentThread().getContextClassLoader();

    /**
     * 文件类型
     */
    private static final String JAR = "jar";
    private static final String FILE = "file";
    private static final String CLASS_FILE = ".class";


    /**
     * 磁盘路径替换成包名
     *
     * @param path 磁盘路径
     * @return java.lang.String
     * @author hongsw
     * @date 2020/12/14 12:00
     **/
    public static String pathToPackage(String path) {
        if (path.startsWith("/")) {
            path = path.substring(1);
        }

        return path.replaceAll("/", ".");
    }

    /**
     * 包名转为磁盘路径名
     *
     * @param packagaName 包名
     * @return java.lang.String
     * @author hongsw
     * @date 2020/12/14 14:01
     **/
    public static String packageToPath(String packagaName) {
        return packagaName.replaceAll("\\.", "/");
    }

    /**
     * 去除文件后缀
     *
     * @param name 文件名
     * @return java.lang.String
     * @author hongsw
     * @date 2020/12/14 14:04
     **/
    public static String trimSuffix(String name) {
        int dotIndex = name.indexOf('.');
        if (-1 == dotIndex) {
            return name;
        }
        return name.substring(0, dotIndex);
    }

    /**
     * 从Jar URL提取路径
     *
     * @param url url
     * @return java.lang.String
     * @author hongsw
     * @date 2020/12/14 14:21
     **/
    public static String distillPathFromJarURL(String url) {
        int startPos = url.indexOf(':');
        int endPos = url.lastIndexOf('!');
        return url.substring(startPos + 1, endPos);
    }


    /**
     * * 将多个对象转换成字符串并连接起来
     *
     * @param objs 对象
     * @return java.lang.String
     * @author hongsw
     * @date 2020/12/14 14:46
     **/
    public static String concat(Object... objs) {
        StringBuilder sb = new StringBuilder(30);
        for (Object obj : objs) {
            sb.append(obj);
        }
        return sb.toString();
    }

    /**
     * 扫描包名下的所有文件
     *
     * @param packName  包名
     * @param classList 注解集合
     * @return java.util.List<java.lang.String>
     * @author hongsw
     * @date 2020/12/14 14:43
     **/
    public static List<String> scan(String packName, List<Class<? extends Annotation>> classList) throws IOException {
        List<String> list = null;
        String packagePath = packageToPath(packName);
        Enumeration<URL> urls = CL.getResources(packagePath);
        while (urls.hasMoreElements()) {
            URL u = urls.nextElement();
            //根据URL判断是JAR包还是文件目录
            if (JAR.equals(u.getProtocol())) {
                String path = distillPathFromJarURL(u.getPath());
                list = scanJar(path, packagePath);
            }
            if (FILE.equals(u.getProtocol())) {
                list = scanFile(u.getPath(), packName);
            }
        }
        if (classList.size() > 0) {
            List<String> newList = new ArrayList<String>(20);
            try {
                assert list != null;
                for (String className : list) {
                    for (Class<? extends Annotation> aClass : classList) {
                        Class<?> clazz = Class.forName(className);
                        if (clazz.isAnnotationPresent(aClass)) {
                            newList.add(className);
                            break;
                        }
                    }
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            return newList;
        } else {
            return list;
        }
    }

    /**
     * 扫描包名下的所有文件
     *
     * @param packName 包名
     * @return java.util.List<java.lang.String>
     * @author hongsw
     * @date 2020/12/14 14:43
     **/
    public static List<String> scan(String packName) throws IOException {
        return scan(packName, new ArrayList<Class<? extends Annotation>>());
    }

    /**
     * 扫描JAR文件
     *
     * @param path        Jar路径
     * @param packagePath 包名
     * @return java.util.List<java.lang.String>
     * @author hongsw
     * @date 2020/12/14 14:27
     **/
    private static List<String> scanJar(String path, String packagePath) throws IOException {
        JarFile jar = new JarFile(path);
        List<String> classNameList = new ArrayList<String>(20);
        Enumeration<JarEntry> entries = jar.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            String name = entry.getName();
            if ((name.startsWith(packagePath)) && (name.endsWith(CLASS_FILE))) {
                name = trimSuffix(name);
                name = pathToPackage(name);
                classNameList.add(name);
            }
        }
        return classNameList;
    }


    /**
     * 扫描文件目录下的类
     *
     * @param path    路径
     * @param basePkg 包名
     * @return java.util.List<java.lang.String>
     * @author hongsw
     * @date 2020/12/14 14:58
     **/
    private static List<String> scanFile(String path, String basePkg) {
        File f = new File(path);
        List<String> classNameList = new ArrayList<String>(10);
        // 得到目录下所有文件(目录)
        File[] files = f.listFiles();
        if (null != files) {
            int len = files.length;
            for (File file : files) {
                // 判断是否还是一个目录
                if (file.isDirectory()) {
                    // 递归遍历目录
                    List<String> list = scanFile(file.getAbsolutePath(), concat(basePkg, ".", file.getName()));
                    classNameList.addAll(list);
                } else if (file.getName().endsWith(CLASS_FILE)) {
                    // 如果是以.class结尾
                    String className = trimSuffix(file.getName());
                    // 如果类名中有"$"不计算在内
                    if (-1 != className.lastIndexOf("$")) {
                        continue;
                    }
                    // 命中
                    String result = concat(basePkg, ".", className);
                    classNameList.add(result);
                }
            }
        }
        return classNameList;
    }

}
